﻿using SlimDX;
using SlimDX.Direct3D10;
using SlimDX.DXGI;

namespace System.Windows.Media.Media3D10
{
    /// <summary>
    /// Contexte de dessin DirectX10
    /// </summary>
    public class DrawingContext
    {
        private static readonly VertexBufferBinding _defaultBinding = new VertexBufferBinding();

        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="DrawingContext"/>
        /// </summary>
        public DrawingContext()
        {
            _device = Engine.Device10;
        }

        private SlimDX.Direct3D10_1.Device1 _device;

        /// <summary>
        /// Matrice de transformation d'un objet dans l'espace monde.
        /// </summary>
        public SlimDX.Matrix WorldMatrix { get; set; }

        /// <summary>
        /// Matrice de transformation d'un objet dans l'espace vue.
        /// </summary>
        public SlimDX.Matrix ViewMatrix { get; set; }

        /// <summary>
        /// Matrice de project d'un objet.
        /// </summary>
        public SlimDX.Matrix ProjectionMatrix { get; set; }

        /// <summary>
        /// Lumière de la scène 3D.
        /// </summary>
        public Light Light { get; set; }

        /// <summary>
        /// Camera de la scène 3D
        /// </summary>
        public Vector3 CameraPosition { get; set; }

        /// <summary>
        /// Technique de rendu de l'objet
        /// </summary>
        public SlimDX.Direct3D10.EffectTechnique Technique { get; set; }

        /// <summary>
        /// Temps d'exécution du moteur en millisecondes
        /// </summary>
        public long Time { get; set; }

        /// <summary>
        /// Dessine un objet dans le pipeline DirectX10
        /// </summary>
        /// <param name="model">Objet à dessiner</param>
        /// <param name="topology">Type de topologie à utiliser</param>
        /// <param name="vertexCount">Nombre de vertices à dessiner</param>
        public void Draw(RenderModel3D10 model, PrimitiveTopology topology, int vertexCount)
        {
            if (model == null) throw new ArgumentNullException("model");

            Draw(model.CurrentEffectTechnique, model.InputLayout, model.GetVertexBufferBinding(), topology, vertexCount);
        }

        /// <summary>
        /// Dessine un objet dans le pipeline DirectX10 en utilisant l'indexation
        /// </summary>
        /// <param name="model">Objet à dessiner</param>
        /// <param name="topology">Type de topologie à utiliser</param>
        /// <param name="indexes">Liste des indices à utiliser</param>
        public void DrawIndexed(RenderModel3D10 model, PrimitiveTopology topology, Int32Collection indexes)
        {
            if (model == null) throw new ArgumentNullException("model");
            if (indexes == null) throw new ArgumentNullException("indexes");

            DrawIndexed(model.CurrentEffectTechnique, model.InputLayout, model.GetVertexBufferBinding(), topology, indexes);
        }

        private void Draw(EffectTechnique technique, InputLayout layout, VertexBufferBinding[] binding, PrimitiveTopology topology, int vertexCount)
        {
            if (technique == null) throw new ArgumentNullException("technique");
            if (layout == null) throw new ArgumentNullException("layout");

            _device.InputAssembler.SetInputLayout(layout);
            _device.InputAssembler.SetPrimitiveTopology(topology);
            _device.InputAssembler.SetIndexBuffer(null, Format.Unknown, 0);
            _device.InputAssembler.SetVertexBuffers(0, binding);

            //Effectue les différentes passes
            for (int pass = 0; pass < technique.Description.PassCount; ++pass)
            {
                technique.GetPassByIndex(pass).Apply();
                Engine.Device10.Draw(vertexCount, 0);
            }
            _device.InputAssembler.SetVertexBuffers(0, _defaultBinding);
        }

        private void DrawIndexed(EffectTechnique technique, InputLayout layout, VertexBufferBinding[] binding, PrimitiveTopology topology, Int32Collection indexes)
        {
            if (technique == null) throw new ArgumentNullException("technique");
            if (layout == null) throw new ArgumentNullException("layout");
            if (indexes == null) throw new ArgumentNullException("indexes");

            //Envoie les données à l'Input Assembler
            _device.InputAssembler.SetInputLayout(layout);
            _device.InputAssembler.SetPrimitiveTopology(topology);
            _device.InputAssembler.SetIndexBuffer(indexes.Buffer, SlimDX.DXGI.Format.R32_UInt, 0);
            _device.InputAssembler.SetVertexBuffers(0, binding);

            //Effectue les différentes passes
            int passCount = technique.Description.PassCount;
            for (int pass = 0; pass < passCount; ++pass)
            {
                technique.GetPassByIndex(pass).Apply();
                Engine.Device10.DrawIndexed(indexes.Count, 0, 0);
            }
            _device.InputAssembler.SetIndexBuffer(null, Format.Unknown, 0);
            _device.InputAssembler.SetVertexBuffers(0, _defaultBinding);
        }
    }
}
